/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                           *
 *                          Copyright (c) 2010                               *
 *                  Alen Stojanov [ ioi.alen@gmail.com ]                     *
 *                Jacobs University Bremen, Bremen, Germany                  *
 *                                                                           *
 *                          All Rights Reserved.                             *
 *                                                                           *
 *    Permission to use, copy, modify and distribute this software and       *
 *    its documentation is hereby granted, provided that both the            *
 *    copyright notice and this permission notice appear in all copies of    *
 *    the software, derivative works or modified versions, and any           *
 *    portions thereof, and that both notices appear in supporting           *
 *    documentation.                                                         *
 *                                                                           *
 *    This code is distributed in the hope that it will be useful, but       *
 *    WITHOUT ANY WARRANTY; without even the implied warranty of             *
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS       *
 *    DISCLAIM ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER          *
 *    RESULTING FROM THE USE OF THIS SOFTWARE.                               *
 *                                                                           *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <QtGui>
#include <QtOpenGL>
#include <QColor>
#include <algorithm>

#include "glpreview.h"
#include "tsdata.h"

using namespace std;

GLPreview::GLPreview(QWidget *parent)
{
	this->setFixedHeight(131);
	repaintTextureMode = false;
	minX = 0; maxX = 0;
	minY = 0; maxY = 0;
	/* make the curent size as the last one */
	lastSize = this->size();
	/* enter the number of x and y grids */
	gridSizeX = 10;
	gridSizeY = 5;
	/* enter ranges such that rangeMinX > range MaxX */
	mappedRangeMinX = 1.0;
	mappedRangeMaxX = 0.0;
	/* enable mouse tracking inside the widget */
	this->setMouseTracking(true);
}

void GLPreview::on_addSeries (QVector<TSData*> series)
{
	/* add the new time series into the vector */
	mutex.lock();
	data += series;
//	for ( int i = 0; i < series.size(); ++i) {
//		data.push_back(series[i]->toBinned(this->size().width(), TSData::Average));
//	}
	mutex.unlock();
	/* repaint the scene */
	repaintPreview();
}


void GLPreview::repaintPreview ()
{
	/* render the preview into a pixmap */
	repaintTextureMode = true;
	repaintTextureWidth = this->size().width();
	for (int i = 0; i < PREVIEW_PIXMAP_COUNT; ++i) {
		repaintTextureModeRatio = (i + 1) * ( 1.0 / (double)(PREVIEW_PIXMAP_COUNT) );
		pixmap[i] = this->renderPixmap();
		/* crop the image */
		pixmap[i] = pixmap[i].copy( 0, 0, (int)floor(pixmap[i].width() * repaintTextureModeRatio), pixmap[i].height() );
	}
	repaintTextureMode = false;

	/* finally, update the openGL */
	this->updateGL();
}

void GLPreview::repaintTexture (double ratio)
{
	mutex.lock();

	if ( data.size() >= 1 )
	{
		/* determine the coordinates of the time series data */
		maxX = data.at(0)->getMaxX(); minX = data.at(0)->getMinX();
		maxY = data.at(0)->getMaxY(); minY = data.at(0)->getMinY();
		for ( int i = 0; i < data.size(); ++i ) {
			maxX = max(maxX, data.at(i)->getMaxX());
			minX = min(minX, data.at(i)->getMinX());
			maxY = max(maxY, data.at(i)->getMaxY());
			minY = min(minY, data.at(i)->getMinY());
		}

		/* set the ranges */
		rangeMinX = minX;
		rangeMaxX = maxX;

		/* use the ratio on the width only */
		gluOrtho2D(minX, minX + (maxX - minX) / ratio, minY, maxY);
		glMatrixMode(GL_MODELVIEW);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();

		double yAxis = 0;

		/* draw a dashed line indicating the 0 x-axis */
		/* also determine the y coordinate of the x-axis */
		if ( minY < 0 && maxY > 0 ) {
			yAxis = 0;
			glColor3f(0.0f, 0.0f, 0.0f);
			glBegin(GL_LINES);
				glVertex3f ( minX, yAxis, 0.0f );
				glVertex3f ( maxX, yAxis, 0.0f );
			glEnd();
		} else if ( minY > 0 ) {
			yAxis = minY;
		} else if ( maxY < 0 ) {
			yAxis = maxY;
		}

		/* draw graph cells */
		double ySpace = (maxY - minY) / (double)gridSizeY;
		for ( int i = 1; i < 5; i++ ) {
			glColor3f(0.9f, 0.9f, 0.9f);
			glBegin(GL_LINES);
				glVertex3f ( minX, minY + i * ySpace, 0.0f );
				glVertex3f ( maxX, minY + i * ySpace, 0.0f );
			glEnd();
		}
		double xSpace = (maxX - minX) / (double)gridSizeX;
		for ( int i = 1; i < 10; i++ ) {
			glColor3f(0.9f, 0.9f, 0.9f);
			glBegin(GL_LINES);
				glVertex3f ( minX + i * xSpace, minY, 0.0f );
				glVertex3f ( minX + i * xSpace, maxY, 0.0f );
			glEnd();
		}

		for ( int i = 0; i < data.size(); ++i ) {
			TSData& tsdata = *data[i];
			QColor lineColor = tsdata.getColor();
			for ( int j = 1; j < tsdata.size(); ++j ) {
//				glColor3f(lineColor.redF() * 1.3f,lineColor.greenF() * 1.3f, lineColor.blueF() * 1.3f);
//				glBegin(GL_QUADS);
//				glVertex3f ( tsdata.getX(j-1), tsdata.getY(j-1),	0.0f );
//				glVertex3f ( tsdata.getX(j)  , tsdata.getY(j)	, 	0.0f );
//				glVertex3f ( tsdata.getX(j)  , yAxis		    , 	0.0f );
//				glVertex3f ( tsdata.getX(j-1), yAxis			, 	0.0f );
//				glEnd();
				glColor3f(lineColor.redF(),lineColor.greenF(), lineColor.blueF());
				// glLineWidth(2.0f);
				glBegin(GL_LINES);
				glVertex3f ( tsdata.getX(j-1), tsdata.getY(j-1), 0.0f );
				glVertex3f ( tsdata.getX(j)  , tsdata.getY(j)  , 0.0f );
				glEnd();
				glLineWidth(1.0f);
			}
		}

		glFlush();
	}

	mutex.unlock();
}

void GLPreview::initializeGL()
{
	/* load a white color on the screen */
	qglClearColor(QColor::fromRgb(255, 255, 255));

	/* enable blending */
	glEnable (GL_BLEND);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	/* enable line-antialiasing */
	glEnable(GL_LINE_SMOOTH);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
}

void GLPreview::paintGL()
{
	/* clear the projection matrix */
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	/* get the viewport */
	QSize viewport = this->size();
	double h = viewport.height();
	double w = viewport.width();

	/* prepare the viewport in case of no data */
	/* and print the default message on the screen */
	if ( data.size() == 0 ) {
		gluOrtho2D(0, w, 0, h);
		glMatrixMode(GL_MODELVIEW);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();
		glColor3f(0.2f, 0.2f, 1.0f);
		this->renderText( w / 2 - 200, h / 2, 0, "The Preview Graph has not been loaded with data");
		glFlush();
	} else {
		if ( repaintTextureMode ) {
			repaintTexture(repaintTextureModeRatio);
		} else {
			gluOrtho2D(0, w, 0, h);
			glMatrixMode(GL_MODELVIEW);
			glClear(GL_COLOR_BUFFER_BIT);
			glLoadIdentity();
			glColor3f(1.0f, 10.f, 1.0f);
			glEnable(GL_TEXTURE_2D);
			/* decide which texture to be mapped */
			int index = (int)round(w / (repaintTextureWidth / (double)(PREVIEW_PIXMAP_COUNT))) - 1;
			if ( index < 0 ) {
				index = 0;
			}
			if ( index >= PREVIEW_PIXMAP_COUNT ) {
				index = PREVIEW_PIXMAP_COUNT - 1;
			}
			this->bindTexture(pixmap[index], GL_TEXTURE_2D);
			glBegin(GL_QUADS);
				glTexCoord2f(0, 0); glVertex2f(0, 0);
				glTexCoord2f(0, 1); glVertex2f(0, h);
				glTexCoord2f(1, 1); glVertex2f(w, h);
				glTexCoord2f(1, 0); glVertex2f(w, 0);
			glEnd();
			glFlush();
			glDisable(GL_TEXTURE_2D);

			double ySpace = h / (double)gridSizeY;
			double xSpace = w / (double)gridSizeX;

			double yRealSpace = (maxY - minY) / (double)gridSizeY;
			double xRealSpace = (maxX - minX) / (double)gridSizeX;

			/* include text for the girds */
			for ( int i = 1; i < 5; i++ ) {
				glColor3f(0.5f, 0.5f, 0.5f);
				this->renderText (0, i * ySpace + 5, 1.0f, QString::number(minY + i * yRealSpace));
			}
			for ( int i = 1; i < 10; i++ ) {
				glColor3f(0.5f, 0.5f, 0.5f);
				this->renderText (i * xSpace, 0, 1.0f, QString::number(minX + i * xRealSpace));
			}

			/* draw the preview rectangle */
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			glColor4f( 0.0f, 0.0f, 0.0f, 0.3f );
			glEnable(GL_BLEND);
			glRectf( mappedRangeMinX, 0.0f, mappedRangeMaxX, h );
			glDisable(GL_BLEND);

			glFlush();
		}
	}
}

void GLPreview::mousePressEvent(QMouseEvent *event)
{
	lastPos = event->pos();
}

void GLPreview::mouseMoveEvent(QMouseEvent *event)
{
	if ( this->mappedRangeMinX < event->x() && event->x() < this->mappedRangeMaxX) {
		this->setCursor(Qt::SizeAllCursor);
	} else {
		this->setCursor(Qt::ArrowCursor);
	}

    if (event->buttons() & Qt::LeftButton) {
    	int dx = event->x() - lastPos.x();
        double w = this->size().width();
    	if ( dx > 0 && dx > w - mappedRangeMaxX ) {
    		dx = w - mappedRangeMaxX;
    	}
    	if ( dx < 0 && dx < 0 - mappedRangeMinX ) {
    		dx = 0 - mappedRangeMinX;
    	}

    	/* calculate the range of the preview graph */
    	rangeMinX += dx / (double)this->width() * (maxX - minX);
    	rangeMaxX += dx / (double)this->width() * (maxX - minX);

    	/* calculate the mapped range of the preview graph */
    	mappedRangeMinX = floor((rangeMinX - minX) / (maxX - minX) * w);
    	mappedRangeMaxX = ceil ((rangeMaxX - minX) / (maxX - minX) * w);

    	updateGL();
    	emit this->rangeChanged(rangeMinX, rangeMaxX);
    	lastPos = event->pos();
    }
}

void GLPreview::on_rangePercent_change (double percent)
{
	/* get the current width */
	int width = this->size().width();

	/* recalculate the center of the range and the size */
	double rangeCenter	= (rangeMaxX + rangeMinX) / 2.0;
	double rangeSize 	= (maxX - minX) * (double)percent / 100.0;

	/* recalculate the range */
	if ( rangeCenter - rangeSize / 2.0 < 0 ) {
		rangeCenter = rangeSize / 2.0;
	}
	if ( rangeCenter + rangeSize / 2.0 > (maxX - minX) ) {
		rangeCenter = (maxX - minX) - rangeSize / 2.0;
	}

	/* refreshe the new actual range */
	rangeMinX = rangeCenter - rangeSize / 2.0;
	rangeMaxX = rangeCenter + rangeSize / 2.0;

	/* calculate the range of the preview graph */
	mappedRangeMinX = floor((rangeMinX - minX) / (maxX - minX) * width);
	mappedRangeMaxX = ceil ((rangeMaxX - minX) / (maxX - minX) * width);

	/* update the gui */
	this->updateGL();
	/* send the update singal */
	emit this->rangeChanged(rangeMinX, rangeMaxX);
}

void GLPreview::resizeGL(int width, int height)
{
	glViewport(0, 0, width, height);
	if ( data.size() == 0 ) {
		/* initialize the range if the preview has not been initialized */
		mappedRangeMinX = floor(width * 2.0 / 5.0);
		mappedRangeMaxX = floor(width * 3.0 / 5.0);
	} else {
		/* recaluclate the range upon resizing */
		mappedRangeMinX = floor(mappedRangeMinX / (double)lastSize.width() * width);
		mappedRangeMaxX = floor(mappedRangeMaxX / (double)lastSize.width() * width);
	}
	lastSize.setWidth(width);
	lastSize.setHeight(height);
}

GLPreview::~GLPreview()
{

}
